#include <brpc/server.h>
#include <butil/logging.h>
#include <map>
//实现语音识别子服务
#include "mysql_user.hpp"
#include "data_es.hpp"
#include "data_redis.hpp"
#include "utils.hpp"
#include "etcd.hpp"     //服务注册
#include "logger.hpp"   //日志模块
#include "channel.hpp"

#include "user.pb.h"
#include "base.pb.h"
#include "file.pb.h"

namespace xchat{
//proto rpc SpeechService ; package xchat
class UserServiceImpl : public xchat::UserService
{
public:
    UserServiceImpl(const std::shared_ptr<elasticlient::Client> &es_client
                    ,const std::shared_ptr<odb::core::database> &mysql_client
                    , const std::shared_ptr<sw::redis::Redis> &redis_client
                    ,const ServiceManager::ptr &channel_manager
                    ,const std::string &file_service_name)
                    : _es_user(std::make_shared<ESUser>(es_client))
                    ,_mysql_user(std::make_shared<UserTable>(mysql_client))
                    ,_redis_session(std::make_shared<Session>(redis_client))
                    ,_redis_status(std::make_shared<Status>(redis_client))
                    ,_redis_codes(std::make_shared<Codes>(redis_client))
                    ,_file_service_name(file_service_name)
                    ,_mm_channels(channel_manager)
    {
        //初始化服务时候创建user索引
        _es_user->createIndex();
    }
    ~UserServiceImpl(){}
    bool nickname_check(const std::string &nickname)
    {
        return nickname.size() > 2 && nickname.size() < 22;
    }
    bool password_check(const std::string &password)
    {
        if(password.size() < 6 && password.size() > 15) 
        {
            LOG_ERROR("密码长度不合法: {} - {}", password, password.size());
            return false;
        }
        for(int i = 0; i < password.size(); ++i)
        {
            if(!((password[i] >= 'a' && password[i] <= 'z') || (password[i] >= 'A' && password[i] <= 'Z') || (password[i] >= '0' && password[i] <= '9') || password[i] == '-' || password[i] == '_'))
            {
                LOG_ERROR("密码包含非法字符: {} - {}", password, password[i]);
                return false;
            }
        }
        return true;
    }
    //注册
    void UserRegister(::google::protobuf::RpcController* controller,
                       const ::xchat::UserRegisterReq* request,
                       ::xchat::UserRegisterRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到用户注册请求");
        brpc::ClosureGuard rpc_guard(done);
        //定义错误处理函数 出错时候被调用
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };
        //从请求中取出昵称和密码    
        std::string nick_name = request->nickname();
        std::string password = request->password();
        //检查昵称是否合法（只能包含字母，数字，连字符-，下划线_，长度限制6~15之间） 
        bool ret = nickname_check(nick_name);
        if(ret == false)
        {
            LOG_ERROR("昵称不合法: {} 请求id: {}", nick_name, request->request_id());
            return error_response(request->request_id(), "昵称不合法 长度在2到22之间");
        }
        //检查密码是否合法（只能包含字母，数字，长度限制6~15之间） 
        ret = password_check(password);
        if(ret == false)
        {
            LOG_ERROR("密码不合法: {} 请求id: {}", password, request->request_id());
            return error_response(request->request_id(), "密码不合法 长度应在6到15之间 或包含非法字符");
        }
        //根据昵称在数据库进行判断是否昵称已存在 
        auto user = _mysql_user->select_by_nickname(nick_name);
        if(user != nullptr)
        {
            LOG_ERROR("昵称已存在: {} 请求id: {}", nick_name, request->request_id());
            return error_response(request->request_id(), "昵称已存在");
        }
        //向数据库新增数据 
        //LOG_DEBUG("开始向数据库中插入数据: {} 请求id: {}", nick_name, request->request_id());

        std::string uid = uuid();
        user = std::make_shared<User>(uid, nick_name, password);
        //LOG_DEBUG("user reset完成");
        ret = _mysql_user->insert(user);
        if(ret == false)
        {
            LOG_ERROR("Mysql数据库新增数据失败: {} 请求id: {}", nick_name, request->request_id());
            return error_response(request->request_id(), "Mysql数据库新增数据失败");
        }
        //向ES服务器中新增用户信息 
        //LOG_DEBUG("开始向redis中插入数据: {} 请求id: {}", nick_name, request->request_id());
        ret = _es_user->appendData(uid, "", nick_name, "", "");        //注册的时候没有手机号 user.proto
        if(ret == false)
        {
            LOG_ERROR("ES服务器新增数据失败: {} 请求id: {}", nick_name, request->request_id());
            return error_response(request->request_id(), "ES服务器新增数据失败");
        }
        //组织响应，进行成功与否的响应即可。
        response->set_request_id(request->request_id());
        response->set_success(true);

    }
    //登录
    void UserLogin(::google::protobuf::RpcController* controller,
                       const ::xchat::UserLoginReq* request,
                       ::xchat::UserLoginRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到用户登录请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };
        //从请求中取出昵称和密码 
        std::string nick_name = request->nickname();
        std::string password = request->password();
        //通过昵称获取用户信息，进行密码是否一致的判断
        auto user = _mysql_user->select_by_nickname(nick_name);
        if(user == nullptr || password != *user->password())
        {
            LOG_ERROR("用户名或密码错误: {}-{} 请求id: {}", nick_name, password, request->request_id());
            return error_response(request->request_id(), "用户名或密码错误");
        }
        //根据redis中的登录标记信息是否存在判断用户是否已经登录。
        bool ret = _redis_status->exists(user->user_id());
        // if(ret == true)
        // {
        //     /////////////////////////////////////////////////
        //     ///应该把人踢下线而不是自己上不去 TODO
        //     /////////////////////////////////////////////////
        //     LOG_ERROR("用户已在其他地方登录: {} 请求id: {}", nick_name, request->request_id()); 
        //     return error_response(request->request_id(), "用户已在其他地方登录!");
        // }
        //构造会话ID，生成会话键值对，向redis中添加会话信息以及登录标记信息 
        std::string session_id = uuid();
        _redis_session->append(session_id, user->user_id());
        _redis_status->append(user->user_id());
        //组织响应，返回生成的会话ID
        response->set_request_id(request->request_id());
        response->set_login_session_id(session_id);
        response->set_success(true);
    }
    bool phone_check(const std::string &phone)
    {
        if(phone.size() != 11) return false;
        if(phone[0] != '1') return false;
        if(phone[1] < '3' || phone[1] > '9') return false;
        for(int i = 2; i < 11; ++i)
        {
            if(phone[i] < '0' || phone[i] > '9') 
                return false;
        }
        return true;
    }
    //获取手机验证码
    void GetPhoneVerifyCode(::google::protobuf::RpcController* controller,
                       const ::xchat::PhoneVerifyCodeReq* request,
                       ::xchat::PhoneVerifyCodeRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到获取手机验证码请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };        
        //从请求中取出手机号码  
        std::string phone = request->phone_number();
        //验证手机号码格式是否正确（必须以1开始，第二位3~9之间，后边9个数字字符）
        bool ret = phone_check(phone);
        if(ret == false)
        {
            LOG_ERROR("手机号码格式不正确: {} 请求id: {}", phone, request->request_id());
            return error_response(request->request_id(), "手机号码格式不正确");
        }
        //生成4位随机验证码 
        std::string code_id = uuid();
        std::string code = "1234";
        //基于短信平台SDK发送验证码  TODO

        //构造验证码ID，添加到redis验证码映射键值索引中 TODO
        ret = _redis_codes->append(code_id, code);
        if(ret == false)
        {
            LOG_ERROR("redis验证码映射键值索引添加失败: {} 请求id: {}", code_id, request->request_id());
            return error_response(request->request_id(), "redis验证码映射键值索引添加失败");
        }
        //组织响应，返回生成的验证码ID 
        response->set_request_id(request->request_id());
        response->set_success(true);
        response->set_verify_code_id(code_id);
    }
    //手机注册
    void PhoneRegister(::google::protobuf::RpcController* controller,
                       const ::xchat::PhoneRegisterReq* request,
                       ::xchat::PhoneRegisterRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到获取手机注册请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };    
        //从请求中取出手机号码和验证码 
        std::string phone = request->phone_number();
        std::string verify_code_id = request->verify_code_id();
        std::string verify_code = request->verify_code();
        //检查注册手机号码是否合法 
        bool ret = phone_check(phone);
        if(ret == false)
        {
            LOG_ERROR("手机号码格式不正确: {} 请求id: {}", phone, request->request_id());
            return error_response(request->request_id(), "手机号码格式不正确");
        }
        //从redis数据库中进行验证码ID-验证码一致性匹配 
        auto vcode = _redis_codes->code(verify_code_id);
        if(!vcode || *vcode != verify_code)
        {
            LOG_ERROR("验证码错误: {} 请求id: {}", verify_code, request->request_id());
            return error_response(request->request_id(), "验证码错误");
        }
        _redis_codes->remove(verify_code_id);
        //通过数据库查询判断手机号是否已经注册过 
        auto user = _mysql_user->select_by_phone(phone);
        if(user != nullptr)
        {
            LOG_ERROR("手机号已经注册过: {} 请求id: {}", phone, request->request_id());
            return error_response(request->request_id(), "手机号已经注册过");
        }
        //向数据库新增用户信息 
        std:string uid = uuid();
        user = std::make_shared<User>(uid, phone);
        ret = _mysql_user->insert(user);
        if(ret == false)
        {
            LOG_ERROR("向数据库新增用户信息失败: {} 请求id: {}", uid, request->request_id());
            return error_response(request->request_id(), "向数据库新增用户信息失败");
        }
        //向ES服务器中新增用户信息 
        ret = _es_user->appendData(uid, phone, uid, "", "");
        //组织响应，返回注册成功与否
        response->set_request_id(request->request_id());
        response->set_success(true);
    }
    //手机登录
    void PhoneLogin(::google::protobuf::RpcController* controller,
                       const ::xchat::PhoneLoginReq* request,
                       ::xchat::PhoneLoginRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到获取手机登录请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };    
        // 从请求中取出手机号码和验证码ID，以及验证码。
        std::string phone = request->phone_number();
        std::string verify_code_id = request->verify_code_id();
        std::string verify_code = request->verify_code();
        // 检查注册手机号码是否合法 
        bool ret = phone_check(phone);
        if(ret == false)
        {
            LOG_ERROR("尝试登录的手机号码格式不正确: {} 请求id: {}", phone, request->request_id());
            return error_response(request->request_id(), "尝试登录的手机号码格式不正确");
        }
        // 从redis数据库中进行验证码ID-验证码一致性匹配 
        auto vcode = _redis_codes->code(verify_code_id);
        if(!vcode || *vcode != verify_code)
        {
            LOG_ERROR("验证码错误: {} 请求id: {}", verify_code, request->request_id());
            return error_response(request->request_id(), "验证码错误");
        }
        _redis_codes->remove(verify_code_id);
        // 根据手机号从数据数据进行用户信息查询，判断用用户是否存在 
        auto user = _mysql_user->select_by_phone(phone);
        if(user == nullptr)
        {
            LOG_ERROR("用户不存在: {} 请求id: {}", phone, request->request_id());
            return error_response(request->request_id(), "用户不存在");
        }
        // 根据redis中的登录标记信息是否存在判断用户是否已经登录。 
        // ret = _redis_status->exists(user->user_id());
        // if(ret == true)
        // {
        //     /////////////////////////////////////////////////
        //     ///应该把人踢下线而不是自己上不去 TODO
        //     /////////////////////////////////////////////////
        //     LOG_ERROR("用户已在其他地方登录: {} 请求id: {}", phone, request->request_id()); 
        //     return error_response(request->request_id(), "用户已在其他地方登录!");
        // }
        //构造会话ID，生成会话键值对，向redis中添加会话信息以及登录标记信息 
        std::string session_id = uuid();
        _redis_session->append(session_id, user->user_id());
        _redis_status->append(user->user_id());
        // 组织响应，返回生成的会话ID
        response->set_request_id(request->request_id());
        response->set_login_session_id(session_id);
        response->set_success(true);
    } 


    //获取用户信息 从这一步开始 是用户登录之后才会进行的操作
    void GetUserInfo(::google::protobuf::RpcController* controller,
                       const ::xchat::GetUserInfoReq* request,
                       ::xchat::GetUserInfoRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到获取用户信息请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };    
        // 从请求中取出用户ID 
        std::string user_id = request->user_id();
        // 通过用户ID，从数据库中查询用户信息 
        auto user = _mysql_user->select_by_id(user_id);
        if(user == nullptr)
        {
            LOG_ERROR("用户不存在: {} 请求id: {}", user_id, request->request_id());
            return error_response(request->request_id(), "用户不存在");
        }
        // 根据用户信息中的头像ID，从文件服务器获取头像文件数据，组织完整用户信息 
        UserInfo *user_info = response->mutable_user_info(); //mutable可变的 拿到指针
        user_info->set_user_id(user->user_id());
        if(user->nickname() && *user->nickname() != "") user_info->set_nickname(*user->nickname());
        if(user->description() && *user->description() != "") user_info->set_description(*user->description());
        if(user->phone() && *user->phone() != "") user_info->set_phone(*user->phone());
     
        if(user->avatar_id() && *user->avatar_id() != "")
        {
            //从信道管理对象中 获取文件管理子服务channel
            auto channel = _mm_channels->choose(_file_service_name);
            if(!channel)
            {
                LOG_ERROR("获取文件服务channel失败: {} 请求id: {}", _file_service_name, request->request_id());
                return error_response(request->request_id(), "获取文件服务channel失败");
            }
            //通过avatarid 进行rpc 调用 获取头像文件数据
            xchat::FileService_Stub stub(channel.get());
            xchat::GetSingleFileReq file_request;
            file_request.set_request_id(request->request_id());
            file_request.set_file_id(*user->avatar_id());
            std::unique_ptr<xchat::GetSingleFileRsp> file_rsp(new xchat::GetSingleFileRsp());
            std::unique_ptr<brpc::Controller> cntl(new brpc::Controller());
            stub.GetSingleFile(cntl.get(), &file_request, file_rsp.get(), nullptr); // 同步调用

            if(cntl->Failed())
            {
                LOG_ERROR("cntl获取文件服务失败: {} 请求id: {} 错误原因: {}", user_id, file_request.request_id(), cntl->ErrorText());
                return error_response(file_request.request_id(), "获取文件服务失败");
            }
            if(!file_rsp->success())
            {
                LOG_ERROR("file_rsp获取文件服务失败: {} 请求id: {} 错误原因: {}", user_id, file_request.request_id(), file_rsp->errmsg());
                return error_response(file_request.request_id(), "获取文件服务失败");
            }
            user_info->set_avatar(file_rsp->file_data().file_content());
        }
        // 组织响应，返回用户信息 
        response->set_request_id(request->request_id());
        response->set_success(true);
        
    }
    //获取多个用户信息
    void GetMultiUserInfo(::google::protobuf::RpcController* controller,
                       const ::xchat::GetMultiUserInfoReq* request,
                       ::xchat::GetMultiUserInfoRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到获取多个用户信息请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };    
        //1. 从请求中取出用户ID列表
        std::vector<std::string> uid_lists;
        for(int i = 0; i < request->users_id_size(); ++i)
        {
            uid_lists.push_back(request->users_id(i));
        }
        //2. 通过用户ID列表，从数据库中查询用户信息
        auto users = _mysql_user->select_multi_users(uid_lists);
        if(users.size() != request->users_id_size())
        {
            LOG_ERROR("提取用户数量不一致{} , {} 请求id: {}", users.size(), request->users_id_size(), request->request_id());
            return error_response(request->request_id(), "提取用户数量不一致");
        }
        //3. 根据用户信息中的头像ID，从文件服务器获取头像文件数据 组织完整用户信息
        auto channel = _mm_channels->choose(_file_service_name);
        if(!channel)
        {
            LOG_ERROR("获取文件服务channel失败{} 请求id: {}",_file_service_name, request->request_id());
            return error_response(request->request_id(), "获取文件服务channel失败");
        }
        xchat::FileService_Stub stub(channel.get());
        xchat::GetMultiFileReq file_request;
        file_request.set_request_id(request->request_id());
        for(auto &user : users)
        {
            if(user.avatar_id() && *user.avatar_id() != "")
            {
                //LOG_DEBUG("用户有头像: {} 请求id: {}", user.user_id(), request->request_id());
                file_request.add_file_id_list(*user.avatar_id());
            }
        }
        std::unique_ptr<xchat::GetMultiFileRsp> file_rsp(new xchat::GetMultiFileRsp());
        std::unique_ptr<brpc::Controller> cntl(new brpc::Controller());
        stub.GetMultiFile(cntl.get(), &file_request, file_rsp.get(), nullptr); // 同步调用
        if(cntl->Failed())
        {
            LOG_ERROR("获取多个文件服务失败 请求id: {} 错误原因: {}", file_request.request_id(), cntl->ErrorText());
            return error_response(file_request.request_id(), "获取多个文件服务失败");
        }
        if(!file_rsp->success())
        {
            LOG_ERROR("file_rsp获取文件服务失败 请求id: {} 错误原因: {}", file_request.request_id(), file_rsp->errmsg());
            return error_response(file_request.request_id(), "获取文件服务失败");
        }
        LOG_DEBUG("获取多个文件服务成功");
        auto user_map = response->mutable_users_info();     //本次响应中的用户信息map
        auto file_map = file_rsp->mutable_file_data();           //批量文件信息的map

        for(auto &user : users)
        {
            UserInfo user_info;
            user_info.set_user_id(user.user_id());
            if (user.nickname() && *user.nickname() != "") user_info.set_nickname(*user.nickname());
            if (user.description() && *user.description() != "") user_info.set_description(*user.description());
            if (user.phone() && *user.phone() != "")user_info.set_phone(*user.phone());
            
            if(user.avatar_id() && *user.avatar_id() != "") user_info.set_avatar(file_map->find(*user.avatar_id())->second.file_content());

            user_map->insert(google::protobuf::MapPair<std::string, xchat::UserInfo>(user.user_id(), user_info));
        }

        //4. 组织响应，返回用户信息列表
        response->set_request_id(request->request_id());
        response->set_success(true);
    }
    //设置用户头像
    void SetUserAvatar(::google::protobuf::RpcController* controller,
                       const ::xchat::SetUserAvatarReq* request,
                       ::xchat::SetUserAvatarRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到设置用户头像请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };    
        // 从请求中取出用户ID与头像数据 
        std::string uid = request->user_id();
        // 从数据库通过用户ID进行用户信息查询，判断用户是否存在 
        auto user = _mysql_user->select_by_id(uid);
        if(!user)
        {
            LOG_ERROR("用户不存在: {} 请求id: {}", uid, request->request_id());
            return error_response(request->request_id(), "用户不存在");
        }
        // 上传头像文件到文件子服务， 
        auto channel = _mm_channels->choose(_file_service_name);
        if(!channel)
        {
            LOG_ERROR("获取文件服务channel失败: {} 请求id: {}", _file_service_name, request->request_id());
            return error_response(request->request_id(), "获取文件服务channel失败");
        }
        xchat::FileService_Stub stub(channel.get());
        xchat::PutSingleFileReq file_request;
        file_request.set_request_id(request->request_id());
        file_request.mutable_file_data()->set_file_name("");
        file_request.mutable_file_data()->set_file_size(request->avatar().size());
        file_request.mutable_file_data()->set_file_content(request->avatar());  
        std::unique_ptr<xchat::PutSingleFileRsp> file_rsp(new xchat::PutSingleFileRsp());
        std::unique_ptr<brpc::Controller> cntl(new brpc::Controller());
        stub.PutSingleFile(cntl.get(), &file_request, file_rsp.get(), nullptr); // 同步调用
        if(cntl->Failed())
        {
            LOG_ERROR("上传文件服务失败: {} 请求id: {} 错误原因: {}", uid, file_request.request_id(), cntl->ErrorText());
            return error_response(file_request.request_id(), "上传文件服务失败");
        }
        if(!file_rsp->success())
        {
            LOG_ERROR("file_rsp获取文件服务失败: {} 请求id: {} 错误原因: {}", uid, file_request.request_id(), file_rsp->errmsg());
            return error_response(file_request.request_id(), "获取文件服务失败");
        }
        std::string avatar_id = file_rsp->file_info().file_id();
        // 将返回的头像文件ID更新到数据库中 
        user->avatar_id(avatar_id);
        bool ret = _mysql_user->update(user);
        if(ret == false)
        {
            LOG_ERROR("Mysql数据库更新头像id失败: {} 请求id: {}", avatar_id, request->request_id());
            return error_response(request->request_id(), "Mysql数据库更新头像id失败");
        }
        // 更新ES服务器中用户信息 
        ret = _es_user->appendData(user->user_id()
                , user->phone() ? *user->phone() : ""
                , user->nickname() ? *user->nickname() : ""
                , user->description() ? *user->description() : ""
                , user->avatar_id() ? *user->avatar_id(): "");

        if(ret == false)
        {
            LOG_ERROR("ES服务器更新用户头像id信息失败: {} 请求id: {}", avatar_id, request->request_id());
            return error_response(request->request_id(), "ES服务器更新用户头像id信息失败");
        }
        // 组织响应，返回更新成功与否 
        response->set_request_id(request->request_id());
        response->set_success(true);
    }
    //设置用户昵称
    void SetUserNickname(::google::protobuf::RpcController* controller,
                       const ::xchat::SetUserNicknameReq* request,
                       ::xchat::SetUserNicknameRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到设置用户昵称请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };    
        // 从请求中取出用户ID与新的昵称 
        std::string uid = request->user_id();
        std::string new_nickname = request->nickname();
        // 判断昵称格式是否正确 
        bool ret = nickname_check(new_nickname);
        if(ret == false)
        {
            LOG_ERROR("昵称格式不正确: {} 请求id: {}", new_nickname, request->request_id());
            return error_response(request->request_id(), "昵称格式不正确");
        }
        // 从数据库通过用户ID进行用户信息查询，判断用户是否存在 
        auto user = _mysql_user->select_by_id(uid);
        if(!user)
        {
            LOG_ERROR("用户不存在: {} 请求id: {}", uid, request->request_id());
            return error_response(request->request_id(), "用户不存在");
        }
        // 将新的昵称更新到数据库中 
        user->nickname(new_nickname);
        ret = _mysql_user->update(user);
        if(ret == false)
        {
            LOG_ERROR("Mysql数据库更新昵称失败: {} 请求id: {}", new_nickname, request->request_id());
            return error_response(request->request_id(), "Mysql数据库更新昵称失败");
        }
        // 更新ES服务器中用户信息 
        ret = _es_user->appendData(user->user_id()
                , user->phone() ? *user->phone() : ""
                , user->nickname() ? *user->nickname() : ""
                , user->description() ? *user->description() : ""
                , user->avatar_id() ? *user->avatar_id(): "");
        if(ret == false)
        {
            LOG_ERROR("ES服务器更新用户昵称信息失败: {} 请求id: {}", new_nickname, request->request_id());
            return error_response(request->request_id(), "ES服务器更新用户昵称信息失败");
        }
        // 组织响应，返回更新成功与否
        response->set_request_id(request->request_id());
        response->set_success(true);

    }
    //设置用户描述
    void SetUserDescription(::google::protobuf::RpcController* controller,
                       const ::xchat::SetUserDescriptionReq* request,
                       ::xchat::SetUserDescriptionRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到设置用户描述请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };    
        // 从请求中取出用户ID与新的签名 
        std::string uid = request->user_id();
        std::string new_description = request->description();
        // 从数据库通过用户ID进行用户信息查询，判断用户是否存在
        auto user = _mysql_user->select_by_id(uid);
        if(!user)
        {
            LOG_ERROR("用户不存在: {} 请求id: {}", uid, request->request_id());
            return error_response(request->request_id(), "用户不存在");
        }
        // 将新的签名更新到数据库中 
        user->description(new_description);
        bool ret = _mysql_user->update(user);
        if(ret == false)
        {
            LOG_ERROR("Mysql数据库更新签名失败: {} 请求id: {}", new_description, request->request_id());
            return error_response(request->request_id(), "Mysql数据库更新签名失败");
        }
        // 更新ES服务器中用户信息 
        ret = _es_user->appendData(user->user_id()
                , user->phone() ? *user->phone() : ""
                , user->nickname() ? *user->nickname() : ""
                , user->description() ? *user->description() : ""
                , user->avatar_id() ? *user->avatar_id(): "");
        if(ret == false)
        {
            LOG_ERROR("ES服务器更新用户签名信息失败: {} 请求id: {}", new_description, request->request_id());
            return error_response(request->request_id(), "ES服务器更新用户签名信息失败");
        }
        // 组织响应，返回更新成功与否
        response->set_request_id(request->request_id());
        response->set_success(true);

    }
    //设置用户手机号
    void SetUserPhoneNumber(::google::protobuf::RpcController* controller,
                       const ::xchat::SetUserPhoneNumberReq* request,
                       ::xchat::SetUserPhoneNumberRsp* response,
                       ::google::protobuf::Closure* done)
    {
        LOG_DEBUG("收到设置用户手机号请求");
        brpc::ClosureGuard rpc_guard(done);
        auto error_response = [this, response](const std::string &rid, const std::string &error_msg) -> void
        {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(error_msg);
        };  
         
        // 从请求中取出手机号码和验证码ID，以及验证码。 
        std::string uid = request->user_id();
        std::string new_phone_number = request->phone_number();
        std::string code = request->phone_verify_code();
        std::string code_id = request->phone_verify_code_id();
        // 检查注册手机号码是否合法 
        bool ret = phone_check(new_phone_number);
        if(!ret)
        {
            LOG_ERROR("手机号格式不正确: {} 请求id: {}", new_phone_number, request->request_id());
            return error_response(request->request_id(), "手机号格式不正确");
        }
        // 从redis数据库中进行验证码ID-验证码一致性匹配 
        auto vcode = _redis_codes->code(code_id);
        if(!vcode || *vcode != code)
        {   
            LOG_ERROR("验证码错误: {} -> {} 请求id: {}", !vcode ? "null" : *vcode , code, request->request_id());
            return error_response(request->request_id(), "验证码错误");
        }
        // 根据手机号从数据数据进行用户信息查询，判断用用户是否存在 
        auto user = _mysql_user->select_by_id(uid);
        if (!user)
        {
            LOG_ERROR("{} - 未找到用户信息 - {}！", request->request_id(), uid);
            return error_response(request->request_id(), "未找到用户信息!");
        }
        // 将新的手机号更新到数据库中 
        user->phone(new_phone_number);
        ret = _mysql_user->update(user);
        if(ret == false)
        {
            LOG_ERROR("Mysql数据库更新手机号失败: {} 请求id: {}", new_phone_number, request->request_id());
            return error_response(request->request_id(), "Mysql数据库更新手机号失败");
        }
        // 更新ES服务器中用户信息 
        ret = _es_user->appendData(user->user_id()
                , user->phone() ? *user->phone() : ""
                , user->nickname() ? *user->nickname() : ""
                , user->description() ? *user->description() : ""
                , user->avatar_id() ? *user->avatar_id(): "");
        if(ret == false)
        {
            LOG_ERROR("ES服务器更新用户手机号信息失败: {} 请求id: {}", new_phone_number, request->request_id());
            return error_response(request->request_id(), "ES服务器更新用户手机号信息失败");
        }
        // 组织响应，返回更新成功与否   
        response->set_request_id(request->request_id());
        response->set_success(true);
    }
private:
    //es客户端相关对象
    ESUser::ptr _es_user; //es用户表
    //mysql客户端相关对象
    UserTable::ptr _mysql_user; //用户表
    //redis客户端相关对象
    Session::ptr _redis_session; //会话
    Status::ptr _redis_status; //状态
    Codes::ptr _redis_codes; //验证码
    //rpc调用客户端相关对象
    std::string _file_service_name;         //文件子服务名称 方便信道通信
    ServiceManager::ptr _mm_channels; //信道管理对象

};
class UserServer
{
    public:
        using ptr = std::shared_ptr<UserServer>;
        UserServer(const Discovery::ptr &service_discovery, 
                const Registry::ptr &reg_client, 
                const std::shared_ptr<elasticlient::Client> &es_client, 
                const std::shared_ptr<odb::core::database> &mysql_client,
                const std::shared_ptr<sw::redis::Redis> &redis_client,
                const std::shared_ptr<brpc::Server> &server)
            :_es_client(es_client)
            ,_mysql_client(mysql_client)
            ,_redis_client(redis_client)
            ,_service_discovery(service_discovery)
            ,_reg_client(reg_client)
            ,_rpc_server(server)
        {}
        ~UserServer()
        {}

        //搭建rpc服务器 并启动服务器
        void start()
        {
            _rpc_server->RunUntilAskedToQuit();
        }
    private:
        std::shared_ptr<elasticlient::Client> _es_client; //es客户端
        std::shared_ptr<odb::core::database> _mysql_client; //数据库连接
        std::shared_ptr<sw::redis::Redis> _redis_client; //redis客户端
        Discovery::ptr _service_discovery; //服务发现对象
        Registry::ptr _reg_client;  //注册中心
        std::shared_ptr<brpc::Server> _rpc_server;       //rpc服务
};
//建筑师模式
class UserServerBuilder
{
    public:
        //构造es客户端对象
        void make_es_object(const std::vector<std::string>& host_list)
        {
            _es_client = ESClientFactory::createClient(host_list);
        }
        //构造mysql客户端对象
        void make_mysql_object(const std::string &user, const std::string &pwd, 
                        const std::string &host, const std::string &db,const std::string &cset,
                        int port, int conn_pool_count)
        {
            _mysql_client = ODBFactory::create(user, pwd, host, db, cset, port, conn_pool_count);
        }
        //构造redis客户端对象
        void make_redis_object(const std::string &host, int port, int db, bool keep_alive)
        {
            _redis_client = RedisFactory::create(host, port, db, keep_alive);
        }
        //用于构造服务发现客户端对象 & 信道管理对象
        void make_discovery_object(const std::string &reg_host, const std::string &base_service_name
                                    , const std::string &file_service_name)
        {
            _file_service_name = file_service_name;
            _mm_channels = std::make_shared<ServiceManager>();
            _mm_channels->declared(file_service_name);   //声明关心那些服务
            auto put_cb = std::bind(&ServiceManager::onServiceOnline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
            auto del_cb = std::bind(&ServiceManager::onServiceOffline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
            _service_discovery = std::make_shared<Discovery>(reg_host, base_service_name, put_cb, del_cb);
        }
        //用于构造服务注册客户端对象  服务中心地址 服务名称 当前实例的外部访问地址
        void make_registry_object(const std::string &reg_host, const std::string server_name, const std::string &access_host)
        {
            _reg_client = std::make_shared<Registry>(reg_host);
            _reg_client->registry(server_name, access_host);
        }
        void make_rpc_server(uint16_t port, uint32_t timeout, uint8_t num_threads)
        {
            if(!_es_client)
            {
                LOG_ERROR(" es客户端对象未构造!");
                abort();
            }
            if (!_mysql_client)
            {
                LOG_ERROR("mysql客户端对象未构造");
                abort();
            }
            if(!_redis_client)
            {
                LOG_ERROR("redis客户端对象未构造!");
                abort();
            }
            if(!_mm_channels)
            {
                LOG_ERROR("信道管理对象未初始化");
                abort();
            }
            _rpc_server = std::make_shared<brpc::Server>();
            UserServiceImpl *user_service = new UserServiceImpl(_es_client, _mysql_client, _redis_client
                , _mm_channels, _file_service_name);
            int ret = _rpc_server->AddService(user_service, brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
            if(ret == -1)
            {
                LOG_ERROR("添加rpc服务失败!");
                abort();
            }
            brpc::ServerOptions options;
            options.idle_timeout_sec = timeout;  //连接空闲超时时间-超时后连接被关闭
            options.num_threads = num_threads;        //io线程数量
            //4.启动服务器
            ret = _rpc_server->Start(port, &options);
            if(ret == -1)
            {
                LOG_ERROR("启动rpc服务器失败!");
                abort();
            }
        }
        UserServer::ptr build()
        {
            //_rpc_server 构造前 其余的得构造好

            if(!_service_discovery)
            {
                LOG_ERROR("服务发现对象未初始化");
                abort();
            }
            if(!_reg_client)
            {
                LOG_ERROR("服务发现对象未初始化");
                abort();
            }
            if(!_rpc_server)
            {
                LOG_ERROR("rpc服务对象未初始化");
                abort();
            }
           
            UserServer::ptr server = std::make_shared<UserServer>(_service_discovery, _reg_client, _es_client, 
                _mysql_client, _redis_client, _rpc_server);
            return server;
        }
    private:
        std::string _file_service_name;         //文件子服务名称 方便信道通信
        Discovery::ptr _service_discovery; //服务发现对象
        Registry::ptr _reg_client;  //注册中心
        std::shared_ptr<brpc::Server> _rpc_server;       //rpc服务
        std::shared_ptr<elasticlient::Client> _es_client; //es客户端
        std::shared_ptr<odb::core::database> _mysql_client; //数据库连接
        std::shared_ptr<sw::redis::Redis> _redis_client; //redis客户端
        ServiceManager::ptr _mm_channels; //信道管理对象
};
}